package cate.game.train.gem;

import cate.common.table.d.GDObj;
import cate.common.table.item.gem.row.GemBaseRow;
import cate.common.util.GameResult;
import cate.game.res.*;
import cate.game.role.Role;
import cate.game.role.bag.equip.Equip;
import cate.game.role.bag.gem.Gem;
import cate.game.role.bag.hero.Hero;
import cate.game.role.bag.item.msg.ItemNumResp;
import cate.game.train.gem.msg.*;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

import static cate.common.table.d.GDGem.*;

public class TrainGemHandler {
    private Hero getHero(Role role, String heroUid) {
        return role.getBag().hero.getItem(heroUid);
    }

    private Equip getEquip(Role role, Hero hero, String equipUid) {
        if (hero == null) {
            return role.getBag().getEquip().getItem(equipUid);
        }
        return hero.equipsOn.equipMap.values().stream().filter(e -> StringUtils.equals(e.uid, equipUid)).findAny().orElse(null);
    }

    /**
     * 获取玺印对象，有英雄id从英雄身上取，没有则从背包里取
     */
    private Gem getGem(Role role, String gemUid, String heroUid, String equipUid) {
        if (heroUid != null || equipUid != null) {
            Hero hero = null;
            if (heroUid != null) {
                hero = getHero(role, heroUid);
            }
            Equip equip = getEquip(role, hero, equipUid);
            if (equip != null) {
                return equip.getGem(gemUid);
            }
        } else {
            return role.getBag().gem.getItem(gemUid);
        }
        return null;
    }

    public void offGem(Role role, String heroUid, String equipUid) {
        Hero hero = null;
        if (StringUtils.isNotBlank(heroUid)) {
            hero = getHero(role, heroUid);
            if (hero == null) {
                role.getGame().notice.message(role, "英雄不存在");
                return;
            }
        }
        Equip equip = getEquip(role, hero, equipUid);
        if (equip == null) {
            role.getGame().notice.message(role, "装备不存在");
            return;
        }
        GameResult<Byte> offRes = equip.offGem(role.toPlayBuildContext(), hero, (byte) 1);
        if (offRes.ok()) {
            role.sendNow(new GemOffResp(heroUid, equipUid));
        } else {
            role.getGame().notice.message(role, offRes.message);
        }
    }

    public GameResult<Void> fastOnGems(Role role, List<String> gemUids, String heroUid, List<String> equipUids) {
        GameResult<Void> r = new GameResult<>();
        if (CollectionUtils.isEmpty(gemUids)) {
            return r.fail("您没有宝石噢");
        }
        if (CollectionUtils.isEmpty(equipUids)) {
            return r.fail("您没有装备噢");
        }
        if (gemUids.size() != equipUids.size()) {
            return r.fail("数据异常噢");
        }
        for (int i = 0; i < equipUids.size(); ++ i) {
            r = onGem(role, gemUids.get(i), heroUid, equipUids.get(i));
            if (!r.ok()) {
                return r;
            }
        }
        return r;
    }

    public GameResult<Void> fastOffGems(Role role, String heroUid) {
        GameResult<Void> r = new GameResult<>();
        Hero hero = null;
        if (StringUtils.isBlank(heroUid) || (hero = getHero(role, heroUid)) == null) {
            return r.fail("英雄不存在");
        }
        if (hero.equipsOn.equipMap.isEmpty()) {
            return r.fail("没有宝石可以卸下");
        }
        for (Equip equip : hero.equipsOn.equipMap.values()) {
            if (!equip.gemsOn.gemMap.isEmpty()) {
                GameResult<Byte> offRes = equip.offGem(role.toPlayBuildContext(), hero, (byte) 1);
                if (offRes.ok()) {
                    role.sendNow(new GemOffResp(heroUid, equip.uid));
                } else {
                    return r.fail(offRes);
                }
            }
        }
        return r;
    }

    public GameResult<Void> onGem(Role role, String gemUid, String heroUid, String equipUid) {
        GameResult<Void> r = new GameResult<>();
        Hero hero = null;
        if (StringUtils.isNotBlank(heroUid)) {
            hero = getHero(role, heroUid);
            if (hero == null) {
                return r.fail("英雄不存在");
            }
        }
        Equip equip = getEquip(role, hero, equipUid);
        if (equip == null) {
            return r.fail("装备不存在");
        }
        if (!equip.getTpl(role.getGame()).canGem) {
            return r.fail("该装备无法镶嵌");
        }
        Gem gem = role.getBag().gem.getItem(gemUid);
        if (gem == null) {
            return r.fail("宝石不存在");
        }
        if (gem.getTpl(role.toPlayBuildContext()).pos != equip.getTpl(role.toPlayBuildContext()).pos) {
            return r.fail("错误的镶嵌部位");
        }
        GameResult<Byte> onRes = onGemRes(role, equip, hero, gem);
        if (!onRes.ok()) {
            return r.fail(onRes.message);
        }
        if (StringUtils.isNotBlank(heroUid)) {
            role.getHistory().action.gemLevelNum();
        }
        role.getHistory().action.gemOnTime();
        role.sendNow(new GemOnResp(heroUid, equipUid, equip.getGemsOn().getPosItem(onRes.data)));
        return r;
    }

    public GameResult<Byte> onGemRes(Role role, Equip equip, Hero hero, Gem gem) {
        GameResult<Byte> onRes = equip.onGem(role.toPlayBuildContext(), hero, gem, (byte) 1);
        if (!onRes.ok()) {
            role.getGame().notice.message(role, onRes.message);
            return onRes;
        }
        if (equip.num > 1) {
            Equip e = new Equip(role, equip.tid, equip.num - 1);
            role.getBag().getEquip().addItem(e, null);
            equip.num = 1;

            ItemNumResp resp = new ItemNumResp(GDObj.Type.EQUIP);
            resp.addChange(equip.uid, 1);
            role.sendNow(resp);
        }
        return onRes;
    }

    public GameResult<Void> recycle(Role role, MixRes gems) {
        GameResult<Void> r = new GameResult<>();
        if (gems == null || gems.isEmpty()) {
            return r.fail("选择的宝石为空");
        }
        if (gems.getItemsCount() > 15) {
            return r.fail("选择的宝石太多拉");
        }
        MixRes reward = new MixRes();
        for (MixResItem item : gems.items) {
            if (item.type != GDObj.Type.GEM) {
                return r.fail("您选择的宝石有误");
            }
            GemBaseRow gemBaseRow = role.getGame().table.gem.base.get(item.tid);
            if (gemBaseRow == null) {
                return r.fail("选择的宝石不存在");
            }
            reward.addList(new MixRes(gemBaseRow.backStr));
        }
        if (reward.isEmpty()) {
            return r.fail("您选择的宝石有误");
        }
        return new MixResConsumer<Void>()
                .res(gems)
                .success(e -> {
                    new MixResAdder()
                            .setSendMailWhenFull(true)
                            .setDialogReward(true)
                            .setRes(reward)
                            .exe(e);
                    return null;
                })
                .exe(role);
    }

    public GameResult<Void> gradeUp(Role role, String heroUid, String equipUid, String gemUid) {
        GameResult<Void> r = new GameResult<>();
        Gem gem = getGem(role, gemUid, heroUid, equipUid);
        if (gem == null) {
            return r.fail("宝石不存在");
        }
        GemBaseRow gemBaseRow = gem.getTpl(role.getGame());
        if (gemBaseRow == null) {
            return r.fail("宝石不存在");
        }
        if (gemBaseRow.isGradeGem) {
            return r.fail("该宝石已经进阶过了");
        }
        if (gemBaseRow.gradeTid == 0) {
            return r.fail("该宝石无法进阶");
        }
        return new MixResConsumer<Void>()
                .tips(true)
                .msg("进阶成功")
                .res(new MixRes(gemBaseRow.gradeConsumeStr))
                .success(e -> {
                    if (StringUtils.isNotBlank(heroUid)) {
                        gem.tid = gemBaseRow.gradeTid;
                        role.sendNow(new GemGradeUpResp(heroUid, equipUid, gem));
                        role.getHistory().action.gemLevelNum();
                        role.getBase().updatePower();
                    } else {
                        role.getBag().gem.removeItemByUid(gem.uid, 1, null);
                        MixResItem rewardItem = new MixResItem(GDObj.Type.GEM, gemBaseRow.gradeTid, 1);
                        new MixResAdder()
                                .setDialogReward(true)
                                .setSendMailWhenFull(true)
                                .setRes(new MixRes().addItem(rewardItem))
                                .exe(role);
                    }
                    role.getHistory().action.gemLevelUpTime();
                    return null;
                })
                .exe(role);
    }

    public GameResult<Void> reforge(Role role, MixRes gems) {
        GameResult<Void> r = new GameResult<>();
        if (gems == null || gems.isEmpty()) {
            return r.fail("选择的宝石为空");
        }
        if (gems.getItemsCount() > 15) {
            return r.fail("选择的宝石太多拉");
        }
        MixRes reward = new MixRes();
        for (MixResItem item : gems.items) {
            if (item.type != GDObj.Type.GEM) {
                return r.fail("您选择的宝石有误");
            }
            GemBaseRow gemBaseRow = role.getGame().table.gem.base.get(item.tid);
            if (gemBaseRow == null) {
                return r.fail("选择的宝石不存在");
            }
            if (!gemBaseRow.isGradeGem) {
                return r.fail("您选择的宝石有误");
            }
            int backGradeId = role.getGame().table.gem.base.getBackGradeId(item.tid);
            if (backGradeId == 0) {
                return r.fail("您选择的宝石有误");
            }
            reward.addList(new MixRes(gemBaseRow.reforgeRewardStr).multiply(item.num));
        }
        if (reward.isEmpty()) {
            return r.fail("您选择的宝石有误");
        }
        return new MixResConsumer<Void>()
                .res(gems)
                .success(e -> {
                    new MixResAdder()
                            .setSendMailWhenFull(true)
                            .setDialogReward(true)
                            .setRes(reward)
                            .exe(e);
                    return null;
                })
                .exe(role);
    }

    public GameResult<Void> generalComposite(Role role, int tid, List<Integer> costTids) {
        GameResult<Void> r = new GameResult<>();
        if (CollectionUtils.isEmpty(costTids)) {
            return r.fail("选择的宝石为空");
        }
        if (costTids.size() != 3) {
            return r.fail("选择的宝石数量有误");
        }
        GemBaseRow baseRow = role.getGame().table.gem.base.get(tid);
        if (baseRow == null) {
            return r.fail("请选择想要合成的属性");
        }
        List<Integer> needIds = baseRow.compositeConsumes;
        if (CollectionUtils.isEmpty(needIds)) {
            return r.fail("该宝石无法合成");
        }
        GemBaseRow costRow = role.getGame().table.gem.base.get(needIds.get(0));
        if (costRow == null) {
            return r.fail("错误的合成材料");
        }
        MixRes consume = new MixRes(costRow.compositeItemConsumeStr);
        for (Integer costTid : costTids) {
            if (!needIds.contains(costTid)) {
                return r.fail("错误的合成材料");
            }
            consume.addItem(new MixResItem(GDObj.Type.GEM, costTid, 1));
        }
        GameResult<MixResItem> consumeRes = consume.consume(role);
        if (!consumeRes.ok()) {
            return r.fail(consumeRes);
        }
        new MixResAdder()
                .setSendMailWhenFull(true)
                .setDialogReward(true)
                .setRes(new MixRes(GDObj.getMixResStr(GDObj.Type.GEM, tid, 1)))
                .exe(role);
        role.getHistory().action.gemCompositeTimes();
        return r;
    }

    public GameResult<Void> composite(Role role, MixRes gems) {
        GameResult<Void> r = new GameResult<>();
        if (gems == null || gems.isEmpty()) {
            return r.fail("选择的宝石为空");
        }
        Map<Integer, Long> hasGems = new HashMap<>();
        for (Gem gem : role.getBag().gem.getList()) {
            hasGems.put(gem.tid, hasGems.getOrDefault(gem.tid, 0L) + gem.num);
        }
        MixRes reward = new MixRes();
        MixRes consume = new MixRes();
        for (MixResItem item : gems.getCombinedItems()) {
            if (item.type != GDObj.Type.GEM) {
                return r.fail("您选择的宝石有误");
            }
            GemBaseRow gemBaseRow = role.getGame().table.gem.base.get(item.tid);
            if (gemBaseRow == null) {
                return r.fail("选择的宝石不存在");
            }
            long costLeastNum = calcLevelNum(gemBaseRow.level, LEAST_GEM_LEVEL) * item.num;
            List<GemBaseRow> consumeRows = getAllCompositeRows(role, gemBaseRow);
            if (costLeastNum == 0) {
                return r.fail("合成材料不足");
            }
            Map<Integer, Map<Integer, Long>> usesLevelMap = calcUses(item.num, costLeastNum, hasGems, consumeRows);
            if (usesLevelMap == null || usesLevelMap.isEmpty()) {
                return r.fail("合成材料不足");
            }
            Map<Integer, Long> combineNumMap = new HashMap<>();
            for (int level=1; level<= gemBaseRow.level;++level) {
                long num = 0;
                if (usesLevelMap.containsKey(level)) {
                    num = usesLevelMap.get(level).values().stream().mapToLong(e -> e).sum();
                }
                num += combineNumMap.getOrDefault(level, 0L);
                if (num == 0){
                    continue;
                }
                long nextNum = num / 3;
                GemBaseRow consumeRow = role.getGame().table.gem.base.getRow(gemBaseRow.protoId, level);
                if (consumeRow == null) {
                    return r.fail("合成材料有误");
                }
                consume.addList(new MixRes(consumeRow.compositeItemConsumeStr).multiply(nextNum));
                combineNumMap.put(level + 1, nextNum);
            }
            reward.addItem(item);
            Map<Integer, Long> usesMap = usesLevelMap.values().stream().flatMap(e -> e.entrySet().stream()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
            usesMap.forEach((k, v) -> consume.addItem(new MixResItem(GDObj.Type.GEM, k, v)));
            hasRemoveUses(hasGems, usesMap);
        }
        GameResult<MixResItem> consumeRes = consume.consume(role);
        if (!consumeRes.ok()) {
            return r.fail(consumeRes);
        }
        new MixResAdder()
                .setSendMailWhenFull(true)
                .setDialogReward(true)
                .setRes(reward)
                .exe(role);
        role.getHistory().action.gemCompositeTimes();
        return r;
    }

    private void hasRemoveUses(Map<Integer, Long> hasGems, Map<Integer, Long> uses) {
        for (Map.Entry<Integer, Long> entry : uses.entrySet()) {
            if (hasGems.containsKey(entry.getKey())) {
                long remain = hasGems.get(entry.getKey()) - entry.getValue();
                if (remain > 0) {
                    hasGems.put(entry.getKey(), remain);
                } else {
                    hasGems.remove(entry.getKey());
                }
            }
        }
    }

    private Map<Integer, Map<Integer, Long>> calcUses(long combineNum, long costLeastNum, Map<Integer, Long> hasGems, List<GemBaseRow> consumeRows) {
        //先确保数量足够
        int num = 0;
        for (GemBaseRow baseRow : consumeRows) {
            if (hasGems.containsKey(baseRow.id)) {
                num += calcLevelNum(baseRow.level, LEAST_GEM_LEVEL) * hasGems.get(baseRow.id);
                if (num >= costLeastNum) {
                    break;
                }
            }
        }
        if (num < costLeastNum) {
            return null;
        }
        LinkedHashMap<Integer, List<GemBaseRow>> levelConsumeRows = new LinkedHashMap<>();
        consumeRows.sort(Comparator.comparingInt(e -> e.level));
        for (GemBaseRow baseRow : consumeRows) {
            levelConsumeRows.putIfAbsent(baseRow.level, new ArrayList<>());
            levelConsumeRows.get(baseRow.level).add(baseRow);
        }
        Map<Integer, Map<Integer, Long>> useMap = new HashMap<>();
        for (List<GemBaseRow> rows : levelConsumeRows.values()) {
            if (rows.isEmpty()) {
                continue;
            }
            long compositNum = calcCompositeNum(rows.get(0).level, useMap);
            long tmpNum = compositNum;
            for (GemBaseRow row : rows) {
                tmpNum += hasGems.getOrDefault(row.id, 0L);
            }
            tmpNum -= tmpNum % 3;
            if (tmpNum <= 0) {
                for (int i = LEAST_GEM_LEVEL; i < rows.get(0).level; ++i) {
                    useMap.remove(i);
                }
                continue;
            }
            tmpNum -= compositNum;
            for (GemBaseRow row : rows) {
                if (tmpNum < 0) {
                    break;
                }
                long hasTidNum = hasGems.getOrDefault(row.id, 0L);
                if (hasTidNum == 0) {
                    continue;
                }
                useMap.putIfAbsent(row.level, new HashMap<>());
                long cost = Math.min(hasTidNum, tmpNum);
                useMap.get(row.level).put(row.id, cost);
                tmpNum -= cost;
                long useLeastNum = calcUseLeastNum(useMap);
                long baseCost = costLeastNum / combineNum;
                int costNum = (int) (useLeastNum / baseCost);
                if (useLeastNum % baseCost == 0 && costNum >= combineNum) {
                    if (costNum == combineNum) {
                        return useMap;
                    } else {
                        Map<Integer, Map<Integer, Long>> newUseMap = new HashMap<>();
                        for (int i = 1; i <= rows.get(0).level; ++ i) {
                            Map<Integer, Long> map = useMap.get(i);
                            if (map == null) {
                                continue;
                            }
                            for (Map.Entry<Integer, Long> entry : map.entrySet()) {
                                int newTid = entry.getKey();
                                long newNum = entry.getValue();
                                for (long k = 1; k <= newNum; ++ k) {
                                    Map<Integer,Long> newUse = newUseMap.computeIfAbsent(i, e -> new HashMap<>());
                                    newUse.put(newTid, newUse.getOrDefault(newTid, 0L) + 1);
                                    if (calcUseLeastNum(newUseMap) == costLeastNum) {
                                        return newUseMap;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    private long calcCompositeNum(int level, Map<Integer, Map<Integer, Long>> useMap) {
        int leastNum = calcLevelNum(level, LEAST_GEM_LEVEL);
        return calcUseLeastNum(useMap) / leastNum;
    }

    private long calcUseLeastNum(Map<Integer, Map<Integer, Long>> useMap) {
        long useLeastNum = 0;
        for (Map.Entry<Integer, Map<Integer, Long>> entry : useMap.entrySet()) {
            long num = entry.getValue().values().stream().mapToLong(e -> e).sum();
            useLeastNum += calcLevelNum(entry.getKey(), LEAST_GEM_LEVEL) * num;
        }
        return useLeastNum;
    }

    private List<GemBaseRow> getAllCompositeRows(Role role, GemBaseRow baseRow) {
        List<GemBaseRow> tids = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(baseRow.compositeConsumes)) {
            List<GemBaseRow> rows = baseRow.compositeConsumes.stream().map(e -> role.getGame().table.gem.base.get(e)).collect(Collectors.toList());
            tids.addAll(rows);
            tids.addAll(getAllCompositeRows(role, rows.get(0)));
        }
        return tids;
    }
}
